package com.example.redisdemo.utils;

import cn.hutool.core.util.StrUtil;
import cn.hutool.db.nosql.redis.RedisDS;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;
import redis.clients.jedis.SortingParams;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

/**
 * redis常用方法封装
 */
public class RedisUtils {
    private static RedisDS redisDS;
    private static RedisUtils    instance;
    private static ReentrantLock lock = new ReentrantLock();

    private RedisUtils() {
    }
    /**
     * 创建单例
     **/
    public static RedisUtils getInstance() {
        if (instance == null) {
            lock.lock();
            if (instance == null) {
                instance = new RedisUtils();
            }
            lock.unlock();
        }
        return instance;
    }

    /**
     * 从jedisPool中获取jedis实例
     **/
    private Jedis getJedis() {
        if (redisDS == null) {
            lock.lock();    //防止吃初始化时多线程竞争问题
            redisDS = RedisDS.create();
            lock.unlock();
        }
        return redisDS.getJedis();
    }

    /**
     * 通用方法：释放Jedis
     *
     * @param jedis
     */
    private void closeJedis(Jedis jedis) {
        jedis.close();
    }

    /***************SCAN迭代匹配前缀key查询***************/
    public static List<String> scanGetKeys(String prefix) {
        if (prefix == null) {
            return null;
        }
        // 游标初始值为0
        String cursor = ScanParams.SCAN_POINTER_START;
        // 查询条件
        ScanParams scanParams = new ScanParams();
        scanParams.match(prefix);// 匹配以 prefix 为前缀的 key
        scanParams.count(1000);  // 每次查询1000条数据
        Jedis jedis = getInstance().getJedis();
        Set<String> keySet = new HashSet<>();
        //循环查询缓存数据
        while (true) {
            //使用scan命令获取数据，使用cursor游标记录位置，下次循环使用
            ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
            cursor = scanResult.getCursor();// 返回0 说明遍历完成
            List<String> keyList = scanResult.getResult();
            keySet.addAll(keyList);
            if (StrUtil.equals(cursor,"0")) { //遍历结束，跳出循环
                break;
            }
        }
        return keySet.size() == 0 ? null : new ArrayList<>(keySet);
    }

    /***************SCAN迭代匹配前缀key查询***************/
    public static void scanDelKeys(String prefix) {
        if (prefix == null) {
            return;
        }
        // 游标初始值为0
        String cursor = ScanParams.SCAN_POINTER_START;
        // 查询条件
        ScanParams scanParams = new ScanParams();
        scanParams.match(prefix);// 匹配以 prefix 为前缀的 key
        scanParams.count(1000);  // 每次查询1000条数据
        Jedis jedis = getInstance().getJedis();
        //循环查询缓存数据
        while (true) {
            //使用scan命令获取数据，使用cursor游标记录位置，下次循环使用
            ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
            cursor = scanResult.getCursor();// 返回0 说明遍历完成
            List<String> keyList = scanResult.getResult();
            for (String key : keyList) {
                del(key);
            }
            if (StrUtil.equals(cursor,"0")) { //遍历结束，跳出循环
                break;
            }
        }
    }

    /***************key相关操作***************/

    /**
     * 设置过期时间
     * @param key key
     * @param seconds 过期时间(秒)
     * @return 返回影响的记录数
     */
    public static long setExpire(String key, int seconds) {
        if (seconds <= 0) {
            return -1L;
        }
        Jedis jedis = getInstance().getJedis();
        long result = jedis.expire(key, seconds);
        getInstance().closeJedis(jedis);
        return result;
    }

    /**
     * 根据key获取过期时间
     * @param key key
     * @return 返回影响的记录数
     */
    public static long getExpire(String key) {
        Jedis jedis = getInstance().getJedis();
        long result = jedis.ttl(key);
        getInstance().closeJedis(jedis);
        return result;
    }

    /**
     * 取消对key过期时间的设置
     *
     * @param key key
     * @return 影响的记录数
     */
    public static long cancelExpire(String key) {
        Jedis jedis = getInstance().getJedis();
        long count = jedis.persist(key);
        getInstance().closeJedis(jedis);
        return count;
    }

    /**
     * 清空所有key
     */
    public static String flushAll() {
        Jedis jedis = getInstance().getJedis();
        String stata = jedis.flushAll();
        getInstance().closeJedis(jedis);
        return stata;
    }

    /**
     * 更改key,仅当新key不存在时才执行
     *
     * @param oldKey 旧key
     * @param newKey 新key
     * @return 状态码
     */
    public static long renamenx(String oldKey, String newKey) {
        Jedis jedis = getInstance().getJedis();
        long status = jedis.renamenx(oldKey, newKey);
        getInstance().closeJedis(jedis);
        return status;
    }

    /**
     * 删除keys对应的记录,可以是多个key
     *
     * @param keys keys
     * @return 删除的记录数
     */
    public static long del(String... keys) {
        Jedis jedis = getInstance().getJedis();
        long count = jedis.del(keys);
        getInstance().closeJedis(jedis);
        return count;
    }

    /**
     * 对List,Set,SortSet进行排序或limit
     *
     * @param key key
     * @param parame 定义排序类型或limit的起止位置.
     * @return List<String> 全部或部分记录
     **/
    public static List<String> sort(String key, SortingParams parame) {
        Jedis jedis = getInstance().getJedis();
        List<String> list = jedis.sort(key, parame);
        getInstance().closeJedis(jedis);
        return list;
    }
    /***************key相关操作***************/


    /***************hash存储相关操作***************/
    /**
     * 往Hash中存入对应关系
     *
     * @param key       Redis键
     * @param field     Hash键
     * @param object    值
     * @return 状态码 1成功，0失败，fieid已存在将更新，也返回0
     **/
    public static long hashPutByField(String key, String field, Object object) {
        Jedis jedis = getInstance().getJedis();
        long s = jedis.hset(key, field, JSONObject.toJSONString(object));
        getInstance().closeJedis(jedis);
        return s;
    }

    /**
     * 往Hash中存入多个对应关系，如果对应关系已存在，则覆盖
     *
     * @param key     Redis键
     * @param object  存储对象
     * @param seconds 过期时间（秒），0为永久
     * @return 状态，成功返回OK
     */
    public static String hashPutAll(String key, Object object, int seconds) {
        Jedis jedis = getInstance().getJedis();
        String s = jedis.hmset(key, objectToRedisMap(object));
        if (seconds > 0) {
            jedis.expire(key,seconds);
        }
        getInstance().closeJedis(jedis);
        return s;
    }

    /**
     * 返回hash中指定存储位置的值
     *
     * @param key   redis键值
     * @param field Hash键
     * @return 存储对应的值
     */
    public static String hashGetField(String key, String field) {
        Jedis jedis = getInstance().getJedis();
        String s = jedis.hget(key, field);
        getInstance().closeJedis(jedis);
        return s;
    }

    /**
     * 以Map的形式返回hash中的存储和值
     *
     * @param key   redis键值
     * @return Map<Strinig,String>
     */
    public static <T>T hashGetAll(String key,Class<T> beanClass) {
        Jedis jedis = getInstance().getJedis();
        Map<String, String> map = jedis.hgetAll(key);
        getInstance().closeJedis(jedis);
        return redisMapToObject(map,beanClass);
    }

    /**
     * 以Map的形式返回hash中的存储和值
     *
     * @param key   redis键值
     * @return Map<Strinig,String>
     */
    public static Map<String, String> hashGetAll(String key) {
        Jedis jedis = getInstance().getJedis();
        Map<String, String> map = jedis.hgetAll(key);
        getInstance().closeJedis(jedis);
        return map;
    }

    /**
     * 从hash中删除指定的存储
     *
     * @param key   redis键值
     * @param fieid 存储的名字
     * @return 状态码，1成功，0失败
     */
    public static long hashDelFieid(String key, String fieid) {
        Jedis jedis = getInstance().getJedis();
        long s = jedis.hdel(key, fieid);
        getInstance().closeJedis(jedis);
        return s;
    }

    /***************hash存储相关操作***************/




    /***************string存储相关操作***************/
    /**
     * 根据key获取记录
     *
     * @param key
     * @return 值
     */
    public static String get(String key) {
        Jedis jedis = getInstance().getJedis();
        String value = jedis.get(key);
        getInstance().closeJedis(jedis);
        return value;
    }

    /**
     * 添加记录,如果记录已存在将覆盖原有的value
     *
     * @param key       键值
     * @param value    值
     * @param seconds 过期时间
     */
    public static String set(String key, String value, int seconds) {
        Jedis jedis = getInstance().getJedis();
        String status = jedis.set(key, value);
        if (seconds > 0) {
            setExpire(key,seconds);
        }
        getInstance().closeJedis(jedis);
        return status;
    }
    /***************string存储相关操作***************/



    /***************list存储相关操作***************/

    /**
     * List长度
     *
     * @param key key
     * @return 长度
     */
    public static long getListSize(String key) {
        Jedis jedis = getInstance().getJedis();
        long count = jedis.llen(key);
        getInstance().closeJedis(jedis);
        return count;
    }

    /**
     * 覆盖操作,将覆盖List中指定位置的值
     *
     * @param key   key
     * @param index 位置
     * @param value 值
     * @return 状态码
     */
    public static String resetValueByIndex(String key, int index, String value) {
        Jedis jedis = getInstance().getJedis();
        String status = jedis.lset(key, index, value);
        getInstance().closeJedis(jedis);
        return status;
    }


    /**
     * 获取List中指定位置的值
     *
     * @param key
     * @param index 位置
     * @return 值
     **/
    public static String getValueByIndex(String key, int index) {
        Jedis jedis = getInstance().getJedis();
        String value = jedis.lindex(key, index);
        getInstance().closeJedis(jedis);
        return value;
    }

    /**
     * 向List中追加记录
     *
     * @param key   key
     * @param value  value
     * @return 记录总数
     */
    public static long lAddValueFromEnd(String key, Object value) {
        if (value == null) {
            return 0;
        }
        List list;
        if (value instanceof List) {
            list = (List) value;
            if (list.size() == 0) {
                return 0;
            }
        } else {
            list = new ArrayList();
            list.add(value);
        }
        String[] values = new String[list.size()];
        for (int i = 0;i < list.size();i++) {
            Object object = list.get(i);
            if (object.getClass() != String.class) {
                values[i] = JSONObject.toJSONString(object);
            } else {
                values[i] = (String) object;
            }
        }
        Jedis jedis = getInstance().getJedis();
        long count = jedis.rpush(key, values);
        getInstance().closeJedis(jedis);
        return count;
    }

    /**
     * 获取指定范围的记录，可以做为分页使用
     *
     * @param key   key
     * @param beanClass 转换类
     * @return List
     */
    public static <T>List<T> lGetAll(String key, Class<T> beanClass) {
        Jedis jedis = getInstance().getJedis();
        List<T> list = lGetByRange(key, 0, getListSize(key),beanClass);
        getInstance().closeJedis(jedis);
        return list;
    }

    /**
     * 获取指定范围的记录，可以做为分页使用
     *
     * @param key   key
     * @param start 开始
     * @param end   如果为负数，则尾部开始计算
     * @return List
     */
    public static <T> List<T> lGetByRange(String key, long start, long end,Class<T> beanClass) {
        Jedis jedis = getInstance().getJedis();
        List<String> list = jedis.lrange(key, start, end);
        List<T> result = new ArrayList<>();
        for (String value : list) {
            if (beanClass == String.class) {
                result.add((T) value);
            } else {
                result.add(JSON.parseObject(value,beanClass));
            }
        }
        getInstance().closeJedis(jedis);
        return result;
    }
    /***************list存储相关操作***************/

    /***************键值递增**********************/
    public static String increment(String key, long delta, int seconds) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        Jedis jedis = getInstance().getJedis();
        Long incrValue = jedis.incrBy(key,delta);
        if (incrValue != null) {
            if (incrValue == 1) {
                //设置超时时间
                if (seconds == 0) {
                    setExpire(key,86400);//设置过期时间为1天
                } else {
                    setExpire(key,seconds);
                }
            }
            if (incrValue >= 9999) {
                incrValue = 1L;
                set(key, String.valueOf(incrValue),0);
                return String.format("%04d", incrValue);
            }
        }
        getInstance().closeJedis(jedis);
        return String.format("%04d", incrValue);
    }
    /***************键值递增**********************/

    //Object转Map
    private static Map<String, String> objectToRedisMap(Object obj)  {
        Map<String, String> redisMap = new HashMap<>();
        Class<?> clazz = obj.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            String fieldName = field.getName();
            Object value = null;
            try {
                value = field.get(obj);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            if (value == null){
                continue;
            }
            if (value.getClass() == LocalDateTime.class) {
                DateTimeFormatter fmt = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
                LocalDateTime dateTime = (LocalDateTime) value;
                redisMap.put(fieldName,dateTime.format(fmt));
            } else if (value.getClass() == LocalDate.class) {
                DateTimeFormatter fmt = DateTimeFormatter.ISO_LOCAL_DATE;
                LocalDate date = (LocalDate) value;
                redisMap.put(fieldName,date.format(fmt));
            } else if (value.getClass() == LocalTime.class) {
                DateTimeFormatter fmt = DateTimeFormatter.ISO_LOCAL_TIME;
                LocalTime time = (LocalTime) value;
                redisMap.put(fieldName,time.format(fmt));
            } else if (value.getClass() != String.class) {
                redisMap.put(fieldName,JSONObject.toJSONString(value));
            } else {
                redisMap.put(fieldName, (String) value);
            }
        }
        return redisMap;
    }

    //Map转Object
    public static <T>T redisMapToObject(Map<String, String> redisMap, Class<T> beanClass) {
        if (redisMap == null || redisMap.size() == 0) {
            return null;
        }
        T obj = null;
        try {
            obj = beanClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        assert obj != null;
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            int mod = field.getModifiers();
            if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
                continue;
            }
            field.setAccessible(true);
            if (redisMap.containsKey(field.getName())) {
                Object value = redisMap.get(field.getName());
                if (value == null) {
                    continue;
                }
                try {
                    if (field.getType() == String.class) {
                        field.set(obj, value);
                    } else if (field.getType() == Long.class) {
                        field.set(obj, Long.valueOf(String.valueOf(value)));
                    } else if (field.getType() == LocalDate.class) {
                        field.set(obj, LocalDate.parse((String) value));
                    } else if (field.getType() == LocalDateTime.class) {
                        field.set(obj, LocalDateTime.parse((String) value));
                    } else if (field.getType() == LocalTime.class) {
                        field.set(obj, LocalTime.parse((String) value));
                    } else if (field.getType() == Integer.class) {
                        field.set(obj, Integer.valueOf(String.valueOf(value)));
                    } else if (field.getType() == Double.class) {
                        field.set(obj, Double.valueOf(String.valueOf(value)));
                    } else if (field.getType() == List.class) {
                        List list = JSONArray.parseArray((String) value,Object.class);
                        field.set(obj, list);
                    }
                } catch (Exception e) {
                    System.out.println(e.getMessage());
                }
            }
        }
        return obj;
    }
}
